﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UltimatePlugin.Technicals;
using Zeta;
using Zeta.Common;
using Zeta.Internals.Actors;
using Zeta.Internals.SNO;

namespace UltimatePlugin.Db.Cache
{
    internal class EnvironmentCache
    {
        static EnvironmentCache()
        {
            CacheManager.MaxRefreshRate = 150;
            CacheManager.CacheObjectGetter = CreateCachedObject;
            CacheManager.CacheObjectRefresher = RefreshCachedObject;
        }

        public static int CurrentWorldID 
        { 
            get; 
            private set; 
        }

        public static bool IsInGame
        {
            get;
            private set;
        }

        public static bool IsLoadingWorld
        {
            get;
            private set;
        }

        public static bool IsPlayingCutscene
        {
            get;
            private set;
        }

        public static void Refresh()
        {
            try
            {
                if (!ZetaDia.IsInGame || ZetaDia.IsLoadingWorld)
                {
                    return;
                }
                using (ZetaDia.Memory.AcquireFrame())
                {
                    ZetaDia.Actors.Update();
                    CurrentWorldID = ZetaDia.CurrentWorldDynamicId;
                    IsInGame = ZetaDia.IsInGame;
                    IsLoadingWorld = ZetaDia.IsLoadingWorld;
                    IsPlayingCutscene = ZetaDia.IsPlayingCutscene;
                    RefreshActor();
                }
            }
            catch
            { }
        }

        private static void RefreshActor()
        {
            foreach (DiaObject obj in ZetaDia.Actors.GetActorsOfType<DiaObject>(true, false).Where(o => o.IsACDBased && o.CommonData != null))
            {
                CacheManager.RefreshObject(obj);
            }
        }

        private static void RefreshCachedObject(int acdGuid, DiaObject diaObject, CachedObject cachedObject)
        {
            if (diaObject == null || cachedObject == null)
            {
                throw new ArgumentNullException();
            }
            switch (cachedObject.CachedType)
            {
                case CachedType.Gizmo:
                    RefreshCachedGizmo((CachedGizmo)cachedObject, false);
                    break;
                case CachedType.Item:
                    RefreshCachedItem((CachedItem)cachedObject, false);
                    break;
                case CachedType.Unit:
                    RefreshCachedUnit((CachedUnit)cachedObject, false);
                    break;
                default:
                    RefreshCachedOther((CachedOther)cachedObject, false);
                    break;
            }
        }

        private static void RefreshCachedOther(CachedOther cachedOther, bool firstRefresh)
        {
            try
            {
                Logger.Write(LogSeverity.Debug, LogCategory.Cache, "Other : {0} : {1}",
                    cachedOther.InternalDiaObject.ActorType,
                    cachedOther.InternalDiaObject.ActorSNO);
            }
            catch
            {
            }
        }

        private static void RefreshCachedGizmo(CachedGizmo cachedGizmo, bool firstRefresh)
        {
            try
            {
                cachedGizmo.Position = cachedGizmo.AcdObject.Position;
                cachedGizmo.CentreDistance = Vector3.Distance(HeroCache.Position, cachedGizmo.Position);
                cachedGizmo.CurrentAnimation = cachedGizmo.AcdObject.CurrentAnimation;
                cachedGizmo.DynamicId = cachedGizmo.AcdObject.DynamicId;

                if (cachedGizmo.InternalDiaObject is DiaGizmo)
                {
                    DiaGizmo gizmo = (DiaGizmo)cachedGizmo.InternalDiaObject;
                    cachedGizmo.HasBeenOperated = gizmo.HasBeenOperated;
                    cachedGizmo.IsDestructible = gizmo.IsDestructible;
                    cachedGizmo.GizmoType = gizmo.ActorInfo.GizmoType;
                    cachedGizmo.Radius = gizmo.CollisionSphere.Radius;
                    cachedGizmo.IsIgnored = false; // Search in SNO List
                }
                Logger.Write(LogSeverity.Normal, LogCategory.Cache, "{0} - {1}", firstRefresh ? "FisrtRefresh" : "     Refresh", cachedGizmo);
            }
            catch { }
        }

        private static void RefreshCachedItem(CachedItem cachedItem, bool firstRefresh)
        {
            try
            {
                cachedItem.Position = cachedItem.AcdObject.Position;
                cachedItem.CentreDistance = Vector3.Distance(HeroCache.Position, cachedItem.Position);
                cachedItem.CurrentAnimation = cachedItem.AcdObject.CurrentAnimation;
                cachedItem.DynamicId = cachedItem.AcdObject.DynamicId;
                if (cachedItem.AcdObject is ACDItem)
                {
                    ACDItem item = (ACDItem)cachedItem.AcdObject;
                    cachedItem.InternalName = item.InternalName;
                    cachedItem.Level = item.Level;
                    cachedItem.FollowerType = item.FollowerSpecialType;
                    cachedItem.GoldAmount = item.Gold;
                    cachedItem.InventoryColumn = item.InventoryColumn;
                    cachedItem.InventoryRow = item.InventoryRow;
                    cachedItem.InventorySlot = item.InventorySlot;
                    cachedItem.IsUnidentified = item.IsUnidentified;
                    cachedItem.ItemBaseType = item.ItemBaseType;
                    cachedItem.Quality = item.ItemQualityLevel;
                    cachedItem.StackQuantity = item.ItemStackQuantity;
                    cachedItem.Type = item.ItemType;
                    
                    cachedItem.Name = item.Name;

                    cachedItem.ArcaneOnCrit = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.ArcaneOnCrit);
                    cachedItem.Armor = (int)Math.Floor(item.Stats.Armor);
                    cachedItem.ArmorBonus = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.ArmorBonus);
                    cachedItem.ArmorTotal = (int)Math.Floor(item.Stats.ArmorTotal);
                    cachedItem.AttackSpeedPercent = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.AttackSpeedPercent);
                    cachedItem.BlockChance = (int)Math.Floor(item.Stats.BlockChance);
                    cachedItem.CritDamagePercent = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.CritDamagePercent);
                    cachedItem.CritPercent = cachedItem.IsUnidentified ? 0 : item.Stats.CritPercent;
                    cachedItem.DamageReductionPhysicalPercent = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.DamageReductionPhysicalPercent);
                    cachedItem.Dexterity = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.Dexterity);
                    cachedItem.GoldFind = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.GoldFind);
                    cachedItem.HatredRegen = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.HatredRegen);
                    cachedItem.HealthGlobeBonus = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.HealthGlobeBonus);
                    cachedItem.HealthPerSecond = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.HealthPerSecond);
                    cachedItem.HealthPerSpiritSpent = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.HealthPerSpiritSpent);
                    cachedItem.Intelligence = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.Intelligence);
                    cachedItem.LifeOnHit = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.LifeOnHit);
                    cachedItem.LifePercent = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.LifePercent);
                    cachedItem.LifeSteal = cachedItem.IsUnidentified ? 0f : item.Stats.LifeSteal;
                    cachedItem.MagicFind = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.MagicFind);
                    cachedItem.ManaRegen = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.ManaRegen);
                    cachedItem.MaxArcanePower = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.MaxArcanePower);
                    cachedItem.MaxDamage = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.MaxDamage);
                    cachedItem.MaxDiscipline = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.MaxDiscipline);
                    cachedItem.MaxFury = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.MaxFury);
                    cachedItem.MaxMana = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.MaxMana);
                    cachedItem.MaxSpirit = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.MaxSpirit);
                    cachedItem.MinDamage = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.MinDamage);
                    cachedItem.MovementSpeed = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.MovementSpeed);
                    cachedItem.PickUpRadius = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.PickUpRadius);
                    cachedItem.RequiredLevel = cachedItem.IsUnidentified ? 0 : item.Stats.RequiredLevel;
                    cachedItem.ResistAll = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.ResistAll);
                    cachedItem.ResistArcane = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.ResistArcane);
                    cachedItem.ResistCold = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.ResistCold);
                    cachedItem.ResistFire = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.ResistFire);
                    cachedItem.ResistHoly = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.ResistHoly);
                    cachedItem.ResistLightning = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.ResistLightning);
                    cachedItem.ResistPhysical = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.ResistPhysical);
                    cachedItem.ResistPoison = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.ResistPoison);
                    cachedItem.Sockets = cachedItem.IsUnidentified ? 0 : item.Stats.Sockets;
                    cachedItem.SpiritRegen = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.SpiritRegen);
                    cachedItem.Strength = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.Strength);
                    cachedItem.Thorns = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.Thorns);
                    cachedItem.Vitality = cachedItem.IsUnidentified ? 0 : (int)Math.Floor(item.Stats.Vitality);
                    cachedItem.WeaponAttacksPerSecond = (int)Math.Floor(item.Stats.WeaponAttacksPerSecond);
                    cachedItem.WeaponDamagePerSecond = (int)Math.Floor(item.Stats.WeaponDamagePerSecond);
                    cachedItem.WeaponDamageType = cachedItem.IsUnidentified ? Zeta.DamageType.None : item.Stats.WeaponDamageType;
                    cachedItem.WeaponMaxDamage = (int)Math.Floor(item.Stats.WeaponMaxDamage);
                    cachedItem.WeaponMinDamage = (int)Math.Floor(item.Stats.WeaponMinDamage);
                    cachedItem.NumSocketsFilled = cachedItem.IsUnidentified ? 0 : item.NumSocketsFilled;

                    if (!cachedItem.IsUnidentified)
                    {
                        int standardRequiredLevel = (item.Stats.Level > 60) ? 60 : item.Stats.Level - 1;
                        cachedItem.LevelReduction = standardRequiredLevel - item.Stats.RequiredLevel;
                    }
                    else
                    {
                        cachedItem.LevelReduction = 0;
                    }
                }
            }
            catch { }
        }

        private static void RefreshCachedUnit(CachedUnit cachedUnit, bool firstRefresh)
        {
            try
            {
                cachedUnit.Position = cachedUnit.AcdObject.Position;
                cachedUnit.CentreDistance = Vector3.Distance(HeroCache.Position, cachedUnit.Position);
                cachedUnit.CurrentAnimation = cachedUnit.AcdObject.CurrentAnimation;
                cachedUnit.DynamicId = cachedUnit.AcdObject.DynamicId;
                cachedUnit.IsElite = cachedUnit.AcdObject.IsElite;
                cachedUnit.IsRare = cachedUnit.AcdObject.IsRare;
                cachedUnit.IsUnique = cachedUnit.AcdObject.IsUnique;
                cachedUnit.MonsterSize = cachedUnit.AcdObject.MonsterInfo.MonsterSize;
                cachedUnit.MonsterType = cachedUnit.AcdObject.MonsterInfo.MonsterType;
                cachedUnit.IsBoss = false; // Search in SNO list
                
                if (cachedUnit.InternalDiaObject is DiaUnit)
                {
                    DiaUnit unit = (DiaUnit)cachedUnit.InternalDiaObject;
                    cachedUnit.HitpointsCurrent = unit.HitpointsCurrent;
                    cachedUnit.IsBurrowed = unit.IsBurrowed;
                    cachedUnit.IsUntargetable = unit.IsUntargetable;
                    cachedUnit.IsInvulnerable = unit.IsInvulnerable;
                    cachedUnit.IsAttackable = !cachedUnit.IsInvulnerable && !cachedUnit.IsUntargetable && !cachedUnit.IsBurrowed && !cachedUnit.IsNpc;
                    if (firstRefresh)
                    {
                        cachedUnit.IsNpc = unit.IsNPC;
                        cachedUnit.IsMinion = unit.SummonedByACDId > 0;
                    }
                }

                Logger.Write(LogSeverity.Normal, LogCategory.Cache, "{0} - {1}", firstRefresh ? "FisrtRefresh" : "     Refresh", cachedUnit);
            }
            catch { }
        }

        private static CachedObject CreateCachedObject(int acdGuid, DiaObject diaObject)
        {
            if (diaObject == null)
            {
                throw new ArgumentNullException();
            }
            CachedObject cachedObject = null;
            switch (diaObject.ActorType)
            {
                case ActorType.Gizmo:
                    cachedObject = new CachedGizmo(diaObject);
                    RefreshCachedGizmo((CachedGizmo)cachedObject, true);
                    break;
                case ActorType.Item:
                    cachedObject = new CachedItem(diaObject);
                    RefreshCachedItem((CachedItem)cachedObject, true);
                    break;
                case ActorType.Unit:
                    cachedObject = new CachedUnit(diaObject);
                    RefreshCachedUnit((CachedUnit)cachedObject, true);
                    break;
                default:
                    cachedObject = new CachedOther(diaObject);
                    RefreshCachedOther((CachedOther)cachedObject, true);
                    break;
            }
            return cachedObject;
        }
    }
}
